﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This class contains a bunch of static/global methods for managing the program memory.
	/// </summary>
	public static class MemoryState
	{
		/// <summary>
		/// List of home memory locations for static variables and locals
		/// </summary>
		private static CHomeLocation[] s_homelocations;

		/// <summary>
		/// The location to set the next home location.
		/// </summary>
		private static int s_openhomepos;

		/// <summary>
		/// List of heap allocated objects
		/// </summary>
		private static Dictionary<int, CObject> s_memory;

		/// <summary>
		/// The counter to keep track of the next address to allocate
		/// </summary>
		private static int s_addrctr;

		/// <summary>
		/// The upper limit on local/static locations
		/// </summary>
		private static readonly int MAX_HOMEVAL = 1024;

		/// <summary>
		/// The upper limit on local/static locations
		/// </summary>
		public static readonly int NULL_ADDRESS = -1;

		/// <summary>
		/// Initialize the memory state of the program
		/// </summary>
		internal static void InitializeMemory()
		{
			s_homelocations = new CHomeLocation[MAX_HOMEVAL];
			s_openhomepos = 0;
			s_memory = new Dictionary<int, CObject>();
			s_addrctr = MAX_HOMEVAL + 1;
		}

		internal static void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			dumpstr.Write(String.Format("MemorySystem\n HomeCount={0} HeapCount={1} AddCtr={2}, HCtr={3}\n", s_homelocations.Length, s_memory.Count, s_addrctr, s_openhomepos));

			for(int i = 0; i < s_homelocations.Length; ++i)
			{
				if(s_homelocations[i] != null)
				{
					s_homelocations[i].SerializeForHeapDump(dumpstr);
					dumpstr.WriteLine();
				}
			}

			foreach(CObject obj in s_memory.Values)
			{
				obj.SerializeForHeapDump(dumpstr);
				dumpstr.WriteLine();
			}
		}

		internal static void SerializeForDGMLDisplay(System.Xml.XmlWriter nodexw, System.Xml.XmlWriter edgexw, HashSet<int> emitaddrs)
		{
			////
			//Var locations are emitted in the localframe or globalframe code

			foreach(CObject obj in s_memory.Values)
			{
				if(emitaddrs.Contains(obj.Address))
					obj.SerializeForDGMLDisplay(nodexw, edgexw, "ignored");
			}
		}

		internal static void ParseAndLoadMemorySystem(System.IO.TextReader rdr)
		{
			InitializeMemory();

			string infoline = rdr.ReadLine();
			Dictionary<string, string> ipkvp = TracingSystem.ParseKeyValuePairs(infoline);
			int homecount = Int32.Parse(ipkvp["HomeCount"]);
			int objcount = Int32.Parse(ipkvp["HeapCount"]);
			s_addrctr = Int32.Parse(ipkvp["AddCtr"]);
			s_openhomepos = Int32.Parse(ipkvp["HCtr"]);

			for(int i = 0; i < homecount; ++i)
			{
				string homeline = rdr.ReadLine();
				Dictionary<string, string> hkvp = TracingSystem.ParseKeyValuePairs(homeline);
				CHomeLocation hloc = CHomeLocation.HomeParseLocation(hkvp);

				s_homelocations[hloc.Address] = hloc;
			}

			for(int i = 0; i < objcount; ++i)
			{
				string objline = rdr.ReadLine();
				Dictionary<string, string> okvp = TracingSystem.ParseKeyValuePairs(objline);
				CObject obj;
				if(okvp["LFlag"].Equals("Array"))
					obj = CArray.ArrayParseLocation(okvp);
				else if(okvp["LFlag"].Equals("Dlgt"))
					obj = CDelegate.DelgateParseLocation(okvp);
				else
					obj = CObject.ObjectParseLocation(okvp);

				s_memory.Add(obj.Address, obj);
			}
		}

		/// <summary>
		/// Resolve the location for a memory address
		/// </summary>
		internal static CLocation ResolveRawAddress(int address)
		{
			Debug.Assert(address != NULL_ADDRESS);

			if(address > MAX_HOMEVAL)
			{
				Debug.Assert(s_memory[address].Address == address);

				return s_memory[address];
			}
			else
			{
				Debug.Assert(s_homelocations[address].Address == address);

				return s_homelocations[address];
			}
		}

		/// <summary>
		/// Resolve the location for a memory address
		/// </summary>
		internal static CLocation ResolveRawAddress_KnownHeap(int address)
		{
			Debug.Assert(address != NULL_ADDRESS && address > MAX_HOMEVAL);
			Debug.Assert(s_memory[address].Address == address);

			return s_memory[address];
		}

		/// <summary>
		/// Add a home location for a static variable of the given type.
		/// </summary>
		internal static CHomeLocation AllocateStaticHomeLocation(IRStaticField sf, IRType locationtype)
		{
			TracingSystem.LogAddNewStaticHomeAlloc(sf, s_openhomepos);

			int addr = s_openhomepos;
			CHomeLocation loc = CHomeLocation.InterpAllocateHomeLocation(addr, locationtype);
			s_homelocations[s_openhomepos] = loc;

			s_openhomepos++;

			return loc;
		}

		/// <summary>
		/// Add a home location for a variable of the given type.
		/// </summary>
		internal static CHomeLocation AllocateHomeLocation(IRVarName v, IRType locationtype)
		{
			TracingSystem.LogAddNewLocalVarHomeAlloc(v, s_openhomepos);

			int addr = s_openhomepos;
			CHomeLocation loc = CHomeLocation.InterpAllocateHomeLocation(addr, locationtype);
			s_homelocations[s_openhomepos] = loc;

			s_openhomepos = Array.IndexOf(s_homelocations, null, s_openhomepos);

			return loc;
		}

		/// <summary>
		/// Remove a home location for a variable with the given address
		/// </summary>
		internal static void FreeHomeLocation(IRVarName v, int addr)
		{
			TracingSystem.LogRemoveLocalVarHomeAlloc(v, addr);

			s_homelocations[addr] = null;
			if(addr < s_openhomepos)
				s_openhomepos = addr;
		}


		/// <summary>
		/// Allocate a heap (class) object of the given type.
		/// </summary>
		internal static CObject AllocateStdHeapObject(IRClassType locationtype)
		{
			TracingSystem.LogNewHeapAllocation(locationtype, s_addrctr + 1);

			int addr = s_addrctr++;
			CObject obj = CObject.InterpAllocateObject(addr, locationtype);
			s_memory.Add(obj.Address, obj);

			return obj;
		}

		/// <summary>
		/// Allocate a heap (delegate) object of the given type.
		/// </summary>
		internal static CDelegate AllocateDelegateObject(IRLambdaType locationtype, IRInvokeIdentity iiv)
		{
			TracingSystem.LogNewHeapAllocation(locationtype, s_addrctr + 1);

			int addr = s_addrctr++;
			CDelegate obj = CDelegate.InterpAllocateObject(addr, locationtype, iiv);
			s_memory.Add(obj.Address, obj);

			return obj;
		}

		/// <summary>
		/// Allocate a heap (array) object of the given type.
		/// </summary>
		internal static CArray AllocateArrayObject(IRArrayType locationtype, NumericValue size)
		{
			TracingSystem.LogNewHeapAllocation(locationtype, s_addrctr + 1);

			int addr = s_addrctr++;
			CArray arr = CArray.InterpAllocateArray(addr, locationtype, size);
			s_memory.Add(arr.Address, arr);

			return arr;
		}

		/// <summary>
		/// Run the garbage collector if needed.
		/// </summary>
		internal static void DoGC()
		{
			if(s_memory.Count > 1000 && s_memory.Count % 100 == 0)
				Console.WriteLine("Hit GC Limit!");
		}
	}
}

